﻿#include "FITKGraphObjectCoordinate.h"

// VTK
#include <vtkPoints.h>
#include <vtkIdList.h>
#include <vtkUnstructuredGrid.h>
#include <vtkProperty.h>
#include <vtkLabelPlacementMapper.h>
#include <vtkFreeTypeLabelRenderStrategy.h>
#include <vtkLabelHierarchy.h>
#include <vtkPointSetToLabelHierarchy.h>
#include <vtkTextProperty.h>
#include <vtkDistanceToCamera.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>

// Graph and filter
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor2D.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKCoordinateAlgorithm.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Data
#include "FITK_Interface/FITKInterfaceModel/FITKCoordinateSystem.h"

namespace Exchange
{
    FITKGraphObjectCoordinate::FITKGraphObjectCoordinate(AbaqusData::FITKDataCase* caseData, Interface::FITKCoordinateSystem* coordinate)
        : FITKGraphObjectFixedSizeBase(caseData, coordinate)
    {
        if (!coordinate)
        {
            return;
        }

        // Initialize the glyph size.
        m_glyphSize = 60.;

        // Initialize the actor.
        initActor();

        // Create actor and initialize properties.
        generateGraph(coordinate);

        // Set the layer need to be rendered.
        setRenderLayer(1);

        // Save the graph information.
        m_graphInfo.Type = CoordinateGraph;
        m_graphInfo.CoordId = coordinate->getDataObjectID();
        
        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = true;
    }

    FITKGraphObjectCoordinate::~FITKGraphObjectCoordinate()
    {
        // Delete data.
        deleteVtkObj(m_points);
        deleteVtkObj(m_gridAxes);
        deleteVtkObj(m_textProperty);
        deleteVtkObj(m_coordFilter);
    }

    void FITKGraphObjectCoordinate::initActor()
    {
        // Get the coordinate axes color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_coordinateColor, color3);

        // Create the grid data.
        m_points = vtkPoints::New();
        m_gridAxes = vtkUnstructuredGrid::New();
        m_gridAxes->SetPoints(m_points);
        vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
        cell->InsertNextId(0);
        m_gridAxes->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

        // Initialize.
        //@{
        m_points->InsertNextPoint(0, 0, 0);
        //@}

        // Create the fix-size filter.
        vtkDistanceToCamera* distToCamera = vtkDistanceToCamera::New();
        distToCamera->SetScreenSize(m_glyphSize);
        distToCamera->SetInputData(m_gridAxes);
        m_distToCameraList.push_back(distToCamera);

        m_coordFilter = FITKCoordinateAlgorithm::New();
        m_coordFilter->SetInputConnection(distToCamera->GetOutputPort());

        // Add the point array name need to be used.      
#if VTK_MAJOR_VERSION < 8
        m_coordFilter->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
        m_coordFilter->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distToCamera->GetDistanceArrayName());
#endif      

        // Create the actor.
        m_fActor = FITKGraphActor::New();
        m_fActor->setGraphObject(this);
        m_fActor->SetPickable(false);
        m_fActor->setScalarVisibility(false);
        m_fActor->GetProperty()->SetRepresentation(1);
        m_fActor->GetProperty()->SetLineWidth(2);
        m_fActor->GetProperty()->SetColor(color3);
        m_fActor->setInputConnection(m_coordFilter->GetOutputPort());
        addActor(m_fActor);

        // Create actor 2D.
        m_fActorLabel2D = FITKGraphActor2D::New();
        m_fActorLabel2D->SetPickable(false);
        m_fActorLabel2D->setGraphObject(this);
        m_fActorLabel2D->setActorType(ActorType2D::TextActor);

        // Text property.
        m_textProperty = vtkTextProperty::New();
        m_textProperty->SetBold(false);
        m_textProperty->SetFontSize(16);
        m_textProperty->SetColor(color3);
        m_textProperty->SetFontFamilyToCourier();

        // Text mapper and strategy.
        //@{
        vtkSmartPointer<vtkPointSetToLabelHierarchy> textData = vtkSmartPointer<vtkPointSetToLabelHierarchy>::New();
        textData->SetInputConnection(m_coordFilter->GetOutputPort());
        textData->SetTargetLabelCount(1);
        textData->SetMaximumDepth(15);
        textData->SetLabelArrayName(m_coordFilter->GetLabelArrayName());
        textData->SetTextProperty(m_textProperty);
        // textData->Update();

        vtkSmartPointer<vtkLabelPlacementMapper> mapperText = vtkSmartPointer<vtkLabelPlacementMapper>::New();
        mapperText->UseDepthBufferOff();
        mapperText->SetShapeToNone();
        mapperText->SetStyleToOutline();
        mapperText->UseUnicodeStringsOff();

        vtkSmartPointer<vtkFreeTypeLabelRenderStrategy> strategyText = vtkSmartPointer<vtkFreeTypeLabelRenderStrategy>::New();

        // Set input connecton of label hierarchy and strategy.
        mapperText->SetInputConnection(textData->GetOutputPort());
        mapperText->SetRenderStrategy(strategyText);

        // Show all labels.
        mapperText->SetPlaceAllLabels(true);
        mapperText->SetMaximumLabelFraction(1.);
        mapperText->SetIteratorType(vtkLabelHierarchy::FULL_SORT);
        m_fActorLabel2D->SetMapper(mapperText);
        //@}

        addActor(m_fActorLabel2D);
    }

    void FITKGraphObjectCoordinate::setCustomOrigin(double x, double y, double z)
    {
        m_customOrigin[0] = x;
        m_customOrigin[1] = y;
        m_customOrigin[2] = z;
    }

    void FITKGraphObjectCoordinate::setUseCustomOrigin(bool flag)
    {
        m_useCustomOrigin = flag;
    }

    void FITKGraphObjectCoordinate::setVisible(bool visibility)
    {
        if (m_fActor)
        {
            m_fActor->SetVisibility(visibility);
        }

        if (m_fActorLabel2D)
        {
            m_fActorLabel2D->SetVisibility(visibility);
        }
    }

    void FITKGraphObjectCoordinate::preHighlight()
    {
        // Get the coordinate axes pre-highlight color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_preHighlightColor, color3);

        // Set the pre-highlight color.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        if (m_textProperty)
        {
            m_textProperty->SetColor(color3);
        }

        FITKGraphObject3D::preHighlight();
    }

    void FITKGraphObjectCoordinate::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Get the coordinate highlight color.
        double color3[3] = { 0 ,0, 0 };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_coordinateHighlightColor, color3);
        }      
        
        // Set the highlight color for lines.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        // Set the highlight color for labels.
        if (m_textProperty)
        {
            m_textProperty->SetColor(color3);
        }

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectCoordinate::disHighlight()
    {
        // Get the coordinate default color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_coordinateColor, color3);

        // Set the default color for lines.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        // Set the default color for labels.
        if (m_textProperty)
        {
            m_textProperty->SetColor(color3);
        }

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectCoordinate::update(bool forceUpdate)
    {
        // Update the visibility only.
        updateVisibility();

        // If the 'forceUpdate' is false, then return, as this graph object cannot be modified.
        if (forceUpdate)
        {
            Interface::FITKCoordinateSystem* coordinate = dynamic_cast<Interface::FITKCoordinateSystem*>(_dataObj);
            if (!coordinate)
            {
                return;
            }

            updateAxes(coordinate);
        }
    }

    void FITKGraphObjectCoordinate::updateVisibility()
    {
        // Update the full actor.
        FITKGraphObject3D::updateVisibility(); 
    }

    bool FITKGraphObjectCoordinate::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        return false;
    }

    void FITKGraphObjectCoordinate::generateGraph(Interface::FITKCoordinateSystem* coordinate)
    {
        if (!coordinate)
        {
            return;
        }

        // Initialize the axes.
        updateAxes(coordinate);
    }

    void FITKGraphObjectCoordinate::updateAxes(Interface::FITKCoordinateSystem* coordinate)
    {
        if (!m_gridAxes || !m_points || !m_coordFilter)
        {
            return;
        }

        // Get the orgin of the coordinate.
        double origin[3]{ 0., 0., 0. };
        coordinate->getOrigin(origin);

        // Get the direction of the coordinate.
        double direc1[3]{ 0., 0., 0. };
        double direc2[3]{ 0., 0., 0. };
        double direc3[3]{ 0., 0., 0. };
        coordinate->getDirection1(direc1);
        coordinate->getDirection2(direc2);
        coordinate->getDirection3(direc3);

        // Update points coordinate.
        if (m_useCustomOrigin)
        {
            m_points->SetPoint(0, m_customOrigin);
        }
        else
        {
            m_points->SetPoint(0, origin);
        }

        m_coordFilter->SetDirectionX(direc1);
        m_coordFilter->SetDirectionY(direc2);
        m_coordFilter->SetDirectionZ(direc3);

        // Get type of the coordinate, and set the label text.
        Interface::FITKCoordinateSystem::FCSysType type = coordinate->getSysType();
        switch (type)
        {
        case Interface::FITKCoordinateSystem::FCSCartesian:
        {
            m_coordFilter->SetAxesLabels("X", "Y", "Z");
            break;
        }
        case Interface::FITKCoordinateSystem::FCSCylinder:
        {
            m_coordFilter->SetAxesLabels("R", "T", "Z");
            break;
        }
        case Interface::FITKCoordinateSystem::FCSSphere:
        {
            m_coordFilter->SetAxesLabels("R", "T", "P");
            break;
        }
        default:
            break;
        }

        m_gridAxes->Modified();
    }

    void FITKGraphObjectCoordinate::getPointByOriginAndDirection(double* origin, double* direc, double* pt)
    {
        if (!origin || !direc || !pt)
        {
            return;
        }

        double md = sqrt(pow(direc[0], 2) + pow(direc[1], 2) + pow(direc[2], 2));
        if (md == 0)
        {
            pt[0] = origin[0];
            pt[1] = origin[1];
            pt[2] = origin[2];
            return;
        }

        for (int i = 0; i < 3; i++)
        {
            pt[i] = origin[i] + direc[i] / md;
        }
    }

    void FITKGraphObjectCoordinate::setPickable(bool isPickable, int dim)
    {
        Q_UNUSED(dim);

        // Set pickable for 2D and 3D actors.
        if (m_fActor)
        {
            m_fActor->SetPickable(isPickable);
        }

        if (m_fActorLabel2D)
        {
            m_fActorLabel2D->SetPickable(isPickable);
        }
    }

    bool FITKGraphObjectCoordinate::getFixedBounds(double* bounds)
    {
        Interface::FITKCoordinateSystem* coordinate = dynamic_cast<Interface::FITKCoordinateSystem*>(_dataObj);
        if (!coordinate)
        {
            return false;
        }

        // Get the location of origin.
        double origin[3]{ 0., 0., 0. };
        coordinate->getOrigin(origin);

        // Set the bounds value with the origin.
        bounds[0] = bounds[1] = origin[0];
        bounds[2] = bounds[3] = origin[1];
        bounds[4] = bounds[5] = origin[2];

        return true;
    }

#ifdef QT_DEBUG
    void FITKGraphObjectCoordinate::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        bool dataVisible = getDataVisibility();

        // Change the actor visibility while changing the camera.
        if (dataVisible)
        {
            if (m_fActorLabel2D)
            {
                m_fActorLabel2D->SetVisibility(!enabled);
            }

            if (m_fActor)
            {
                m_fActor->SetVisibility(!enabled);
            }
        }
    }
#endif // QT_DEBUG
}   // namespace Exchange